package com.apobates.forum.thrones.admin;

import com.apobates.forum.core.impl.CoreAppConfig;
import com.apobates.forum.letterbox.impl.LetterAppConfig;
import com.apobates.forum.member.impl.MemberAppConfig;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.lang.CommonDoubleBean;
import com.apobates.forum.utils.lang.CommonDoubleBeanDeserializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 后端Spring framework配置类
 * @author xiaofanku
 * @since 20200603
 */
@Configuration
@PropertySource("classpath:global.properties")
@EnableLoadTimeWeaving
@EnableAspectJAutoProxy(proxyTargetClass=true)
@EnableTransactionManagement(proxyTargetClass = true)
@ComponentScan(basePackages = {"com.apobates.forum.thrones.admin.event"}, useDefaultFilters = false, includeFilters = {@ComponentScan.Filter(classes = {org.springframework.stereotype.Component.class})})
@Import(value = {MemberAppConfig.class, LetterAppConfig.class, CoreAppConfig.class, ThronesAdminRedisEventConfig.class})
public class ThronesAdminAppConfig {
    @Autowired
    private Environment env;
    //
    @Bean
    public JdbcProperties getJdbcProperties() {
        JdbcProperties jp = new JdbcProperties();
        jp.setDriverClassName(env.getProperty("jdbc.driverClassName"));
        jp.setUrl(env.getProperty("jdbc.url"));
        jp.setUsername(env.getProperty("jdbc.username"));
        jp.setPassword(env.getProperty("jdbc.password"));
        return jp;
    }
    // 数据源
    @Bean(value = "dataSource") // 假如没有指定名字，默认为方法名
    public DataSource buildDataSource(JdbcProperties jdbcProperties) {
        //https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
        HikariConfig config = new HikariConfig();
        config.setDriverClassName(jdbcProperties.getDriverClassName());
        config.setJdbcUrl(jdbcProperties.getUrl());
        config.setUsername(jdbcProperties.getUsername());
        config.setPassword(jdbcProperties.getPassword());
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.addDataSourceProperty("useServerPrepStmts", "true");
        config.addDataSourceProperty("useLocalSessionState", "true");
        config.addDataSourceProperty("rewriteBatchedStatements", "true");
        config.addDataSourceProperty("cacheResultSetMetadata", "true");
        config.addDataSourceProperty("cacheServerConfiguration", "true");
        config.addDataSourceProperty("elideSetAutoCommits", "true");
        config.addDataSourceProperty("maintainTimeStats", "false");
        return new HikariDataSource(config);
    }
    //ADD PERSISTENCE SUPPORT HERE (jpa, hibernate, etc)
    @Bean
    public JpaVendorAdapter getJpaVendorAdapter() {
        EclipseLinkJpaVendorAdapter elva = new org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter();
        elva.setGenerateDdl(true);
        elva.setShowSql(true);
        elva.setDatabase(Database.MYSQL);
        elva.setDatabasePlatform("org.eclipse.persistence.platform.database.MySQLPlatform");
        return elva;
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
        LocalContainerEntityManagerFactoryBean aemf = new org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean();
        aemf.setPersistenceUnitName(env.getProperty("jpa.unit-name"));
        aemf.setDataSource(dataSource);
        aemf.setPersistenceXmlLocation("classpath:META-INF/persistence.xml");
        aemf.setJpaVendorAdapter(jpaVendorAdapter);
        aemf.setJpaDialect(new org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect());
        return aemf;
    }
    
    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
        JpaTransactionManager jtm = new org.springframework.orm.jpa.JpaTransactionManager();
        jtm.setEntityManagerFactory(emf);
        return jtm;
    }
    //springcache for redis
    @Bean
    public JedisConnectionFactory redisConnectionFactory(Environment env) {
        String redisHost = env.getProperty("cache.redis.host");
        int redisPort = Commons.stringToInteger(()->env.getProperty("cache.redis.port"), 6379);
        String redisPassword = env.getProperty("cache.redis.password");
        int redisDatabase = Commons.stringToInteger(()->env.getProperty("cache.redis.database"), 0);
        long redisTimeout = Commons.stringToLong(()->env.getProperty("cache.redis.timeout"), 60L);
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration(redisHost, redisPort);
        redisConfig.setPassword(redisPassword);
        redisConfig.setDatabase(redisDatabase);
        //pool
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(100);
        jedisPoolConfig.setMinIdle(10);
        jedisPoolConfig.setMaxTotal(1000);
        jedisPoolConfig.setMaxWaitMillis(1000L);
        
        JedisClientConfiguration clientConfig = JedisClientConfiguration.builder()
                .connectTimeout(Duration.ofSeconds(redisTimeout))
                .readTimeout(Duration.ofSeconds(redisTimeout))
                .usePooling()
                .poolConfig(jedisPoolConfig)
                .build();
        return new JedisConnectionFactory(redisConfig, clientConfig);
    }
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory cf, GenericJackson2JsonRedisSerializer valuesRedisSerializer) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(cf);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(valuesRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(valuesRedisSerializer);
        redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, GenericJackson2JsonRedisSerializer valuesRedisSerializer) {
        RedisCacheConfiguration config = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofDays(1))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valuesRedisSerializer));
        return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
    }
    @Bean("valuesRedisSerializer")
    public GenericJackson2JsonRedisSerializer redisSerializer(){
        JavaTimeModule jtm = new JavaTimeModule();
        jtm.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        jtm.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        jtm.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        jtm.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        jtm.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        jtm.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        //
        SimpleModule cusModule = new SimpleModule("CommonDoubleBeanDeserializer");
        cusModule.addDeserializer(CommonDoubleBean.class, new CommonDoubleBeanDeserializer());
        ObjectMapper mapper = new ObjectMapper()
                .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
                .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
                .registerModule(jtm)
                .registerModule(new Jdk8Module())
                .registerModule(cusModule)
                .findAndRegisterModules()
                .activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return new GenericJackson2JsonRedisSerializer(mapper);
    }
}